<?php
/**
 * Abstraktny datovy model.
 * Sluzi ako implicitny objektovo-relacny mapper
 * @author Tomas Srna
 * @package bakalarka
 */

abstract class Model
{
	/*
	 * Podtrzitko je kvoli snahe vyhnut sa zhode s nazvami poli
	 */
	
	protected $_table = '';
	protected $_fields = array();
	protected $_engine = 'InnoDB';
	protected $_encoding = 'utf8';
	protected $_new = false;
	protected $_pkeys_values = array();
	
	/**
	 * Ziskame table
	 * @return string Table
	 */
	public function table()
	{
		return $this->_table;
	}
	
	/**
	 * Ziskame polia
	 * @return array 	Polia
	 */
	public function fields()
	{
		return $this->_fields;
	}
	
	/**
	 * Konstruktor zavola _init()
	 * Je protected, triedy sa nevytvaraju bez obsahu
	 */
	protected function __construct()
	{
		$this->_init();
	}
	
	/**
	 * Vytvorenie prazdneho objektu pre strukturu
	 * (late static binding)
	 * @return Model Prazdny objekt
	 */
	final public static function struct()
	{
		$cc = get_called_class();
		return new $cc();
	}
	
	/**
	 * Tvorba noveho objektu
	 * (late static binding) 
	 * @param array $values Hodnoty poli noveho objektu
	 * @return Model Novy objekt
	 */
	public static function create(array $values)
	{
		$cc = get_called_class();
		$o = new $cc();
		
		foreach($o->_fields as $f)
		{	
			if(array_key_exists($f->name, $values))
				$o->{$f->name} = $values[$f->name];
			else if(!$f->allownull && !$f->autoincrement)
				throw new Exception('L::mustNotBeNull;'.json_encode(array($f->name)));
		}
		
		$o->_new = true;
		
		return $o;
	}
	
	/**
	 * Nacitanie objektu z databazy
	 * @param array $pkeys Mnozina primarnych klucov
	 * @return Model Nacitany objekt
	 */
	final public static function load(array $pkeys, $adaptor = NULL)
	{
		if($adaptor == NULL)
			$db = Db::instance();
		else
			$db = $adaptor;
		
		$cc = get_called_class();
		$o = new $cc();
		
		$o->_pkeys_values = $pkeys;
		
		$r = $db->query("SELECT * FROM `$o->_table` WHERE ".$o->where().";")->fetchObject();
		if($r == NULL)
			return NULL;
		
		foreach ($o->_fields as $f)
		{
			if(property_exists($r, $f->name) && isset($r->{$f->name}) )
				$o->{$f->name} = $r->{$f->name};
		}
		
		$o->_new = false;
		
		return $o;
	}
	
	/**
	 * Ulozenie zaznamu do databazy
	 * @param PDO $adaptor Adapter databazoveho spojenia
	 */
	public function save($adaptor = NULL)
	{
		if($adaptor == NULL)
			$db = Db::instance();
		else
			$db = $adaptor;
		
		return $db->query($this->sql());
	}
	
	/**
	 * Tvorba where klauzy z primarnych klucov 
	 * pre pouzitie v datbazovych dotazoch
	 * @param array $values Hodnoty primarnych klucov
	 * @return string where klauza
	 */
	protected function where(array $values = NULL, $adaptor = NULL)
	{
		if($adaptor == NULL)
			$db = Db::instance();
		else
			$db = $adaptor;
		
		if($values == NULL)
			$values = $this->_pkeys_values;
		
		$primary_keys = array();
		foreach ($this->_fields as $f)
		{
			if($f->pk)
				$primary_keys[] = $f->name;
		}
		
		$c = count($primary_keys);
		$pkeys_where = "";
		foreach($primary_keys as $pk)
		{
			--$c;
			if(!array_key_exists($pk, $values))
				throw new Exception('L::primaryKeyRequired;'.
					json_encode(array($this->_table, $pk)));
			$pk_q = $db->quote($values[$pk]);
			$pkeys_where.="`$pk` = $pk_q";
			if($c>0)
				$pkeys_where .= " AND ";
		}
		
		return $pkeys_where;
	}
	
	/**
	 * Konsolidacia identity po nacitani z DB 
	 * (nestandardna instantiacia)
	 * Pouzivat s rozumom!
	 */
	public function consolidate()
	{
		$primary_keys = array();
		foreach ($this->_fields as $f)
		{
			if($f->pk)
				$primary_keys[] = $f->name;
		}
		
		foreach($primary_keys as $pk)
		{
			if(property_exists($this, $pk))
				$this->_pkeys_values[$pk] = $this->{$pk};
		}
	}
	
	/**
	 * Vymazanie riadka instancie
	 * @param PDO $adaptor Adapter databazoveho spojenia
	 * @return bool Podarilo sa?
	 */
	public function delete($adaptor = NULL)
	{
		if($this->_new)
			return false;
		return self::deletePK($this->_pkeys_values);
	}
	
	/**
	 * Vymazanie riadka definovaneho mnozinou primarnych klucov
	 * @param array $pkeys Mnozina PK
	 * @param PDO $adaptor Adapter databazoveho spojenia
	 * @return bool Podarilo sa?
	 */
	public static function deletePK($pkeys, $adaptor = NULL)
	{
		if($adaptor == NULL)
			$db = Db::instance();
		else
			$db = $adaptor;
		
		$cc = get_called_class();
		$o = new $cc();
		
		$db->query("DELETE FROM `{$o->_table}` WHERE ".$o->where($pkeys).";");
		
		return true;
	}
	
	/**
	 * SQL struktura
	 * @return string SQL
	 */
	public function sqlStructure()
	{
		$r = "";
		$pk = array();
		if($this->_table == "" || count($this->_fields) < 1)
			return $r;
		$r .= "CREATE TABLE IF NOT EXISTS `$this->_table` (";
		foreach ($this->_fields as $f) {
			$r .= "\n  ".$f.",";
			if($f->pk)
				$pk[] = $f->name; 
		}
		if(count($pk) > 0)
			$r .= "\n  PRIMARY KEY(`".implode("`, `", $pk)."`),";
		$r = trim($r, ",");
		$r .= "\n) ENGINE=$this->_engine DEFAULT CHARSET=$this->_encoding;";
		
		return $r;
	}
	
	/**
	 * Nastavenie parametrov z pola
	 * @param array $params 	Parametre
	 */
	public function set(array $params)
	{
		foreach($params as $k => $v)
		{
			$this->{$k} = $v;
		}
	}
	
	/**
	 * Pretypovanie na string da Model(PK)
	 * @return string Model(PK)
	 */
	public function __toString()
	{
		$r = "";
		$cc = get_called_class();
		
		$r .= $cc;
		$r .= '(';
		$values = array();
		foreach ($this->_fields as $f)
			$values[$f->name] = $this->{$f->name};
		$r .= implode(", ", $values);
		$r .= ')';
		
		return $r;
	}
	
	/**
	 * SQL Data riadka
	 * @param PDO $adaptor
	 * @return string SQL
	 */
	 public function sql($adaptor = NULL)
	 {
	 	if($adaptor == NULL)
			$db = Db::instance();
		else
			$db = $adaptor;
	 	/**
		 * Ziskame polia ktore su vyplnene a mnozinu primarnych klucov
		 */
		$values = array();
		$primary_keys = array();
		foreach ($this->_fields as $f)
		{
			if(property_exists($this, $f->name) && isset($this->{$f->name}) )
				$values[$f->name] = $this->{$f->name};
			if($f->pk)
				$primary_keys[] = $f->name;
		}
		
	 	$r = "";
	 	/**
		 * Ak sa jedna o novy objekt, pouzijeme INSERT
		 */
	 	if($this->_new)
		{
			$r .= "INSERT INTO `$this->_table` (`";
			$r .= implode("`, `", array_keys($values));
			$r .= "`) VALUES (";
			$c=count($values);
			foreach($values as $v)
			{
				--$c;
				if($v === "NULL" || $v === "")
					$r .= 'NULL';
				else if ($v instanceof Expression)
					$r .= "$v";
				else
					$r .= $db->quote($v);
				if($c>0)
					$r .= ", ";
			}
			$r .= ");";
		}
		
		/**
		 * Ak sa jedna o existujuci objekt, pouzijeme UPDATE
		 */
		if(!$this->_new)
		{
			$r .= "UPDATE `$this->_table` SET ";
			$c=count($values);
			foreach($values as $k => $v)
			{
				--$c;
				if($v === "NULL" || $v === "")
					$r .= "`$k` = NULL";
				else if($v instanceof Expression)
					$r .= "`$k` = $v";
				else
					$r .= "`$k` = " . $db->quote($v);
				if($c>0)
					$r .= ", ";
			}
			$r .= " WHERE ";
			$c = count($primary_keys);
			foreach($primary_keys as $pk)
			{
				--$c;
				$r.="`$pk` = '{$this->_pkeys_values[$pk]}'";
				if($c>0)
					$r .= " AND ";
			}
			$r .= ";";
		}
		return $r;
	 }
} 
